﻿/*
 * Name:        Snake
 * Description: A set of classes that define the snake itself
 * 
 * Containts:
 *      Snake - A snake class that holds all snake body parts, and controls snake movement, drawing and such
 *      SnakeBody - Snake body
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections;
using System.Drawing;

using Gyvate.Util;
using Gyvate.Field;
using Gyvate.Worldspawn;

namespace Gyvate.Entities
{
    internal class Snake
    {
        private LinkedList<SnakeBody> m_body;
        private SnakeBody m_head;
        private PlayField m_playField;

        private Game m_game;

        private Vector2d m_vecLastMoveVec;
        private Vector2d m_vecLastTailPos;

        private int m_iSnakeSize;
        private bool m_bDidSnakeEat;
        private int m_iSmoothingLeft;

        public SnakeBody GetHead() { return m_head; }   //Returns current head entity
        public LinkedList<SnakeBody> GetBodyList() { return m_body; }   //Returns body entity list

        public Vector2d GetLastTailPos() { return m_vecLastTailPos; }
        public int GetSmooth() { return m_iSmoothingLeft; }

        public Vector2d GetLastMoveVec() { return m_vecLastMoveVec; }
       

        /*
         * Constructor
         * Parameters:
         *      plField - PlayField in which snake "lives"
         *      headPos - First snake dot place
         *      rotation - head rotation (can only be 0, 90, 180, 270)
         *      snakeSize - The sizeMainPanel of the snake
         */
        public Snake(Game game, Vector2d headPos, int rotation, int snakeSize)
        {
            switch (rotation)
            {
                case 0:
                case 360:
                default:
                    m_vecLastMoveVec = new Vector2d(1, 0);
                    break;

                case 90:
                    m_vecLastMoveVec = new Vector2d(0, 1);
                    break;

                case 180:
                    m_vecLastMoveVec = new Vector2d(-1, 0);
                    break;

                case 270:
                    m_vecLastMoveVec = new Vector2d(0, -1);
                    break;
            }

            m_game = game;
            m_playField = m_game.GetPlayField();

            m_game.PrecacheSprite(Constants.SPRITE_HEAD);
            m_game.PrecacheSprite(Constants.SPRITE_BODY);

            m_body = new LinkedList<SnakeBody>();
            m_iSnakeSize = 1;
            m_head = new SnakeBody(headPos, game);

            if( m_head != null )
                m_body.AddLast(m_head);

            for (int i = 1; i < snakeSize; i++)
                AddBodyPart();

            m_vecLastTailPos = m_body.Last.Value.GetPos();
        }

        /*
         * Sets eaten state to true
         */
        public void SetAte()
        {
            m_bDidSnakeEat = true;
        }

        /*
         * Adds a snake body part
         */
        public void AddBodyPart()
        {
            m_iSnakeSize++;

            Vector2d newPos = new Vector2d(m_body.Last.Value.GetPos());
            newPos += (m_vecLastMoveVec * -1);
            SnakeBody newPart = new SnakeBody(newPos, m_game);

            m_body.AddLast(newPart);
        }

        /*
         * Calculates how many times can we do during this move period
         */
        private int CalculateSmoothFactor()
        {
            return 1;
        }

        /*
         * Draws the snake on screen
         * Debug parameters:
         *      toConsole - Draws to console
         */
        public void Draw(bool toConsole = false)
        {
            /*if (m_iSmoothingLeft <= 0) //Check if smoothing is necessery
            {
                Console.WriteLine("Smooth finished");
                for (LinkedListNode<SnakeBody> it = m_body.First; it != null; it = it.Next)
                    it.Value.Draw(toConsole);
            }
            else*/
            {
                for (LinkedListNode<SnakeBody> it = m_body.Last; it != null; it = it.Previous)
                    it.Value.Draw(it);

                if (m_iSmoothingLeft > 0)
                    m_iSmoothingLeft -= CalculateSmoothFactor();
            }
        }



#if null
        /*
         * Moves snake based on previous position
         * Usage: Use when no input is specified
         */
        public void MoveSnake()
        {
            Vector2d newPos = new Vector2d(m_head.GetPos() + m_vecLastMoveVec);

            Dimensions2d fieldDim = m_playField.GetDimensions();

            //Apply field fix X
            if (newPos.x >= fieldDim.size.x + fieldDim.pos.x)
                newPos.x = fieldDim.pos.x;
            else if (newPos.x <= fieldDim.pos.x)
                newPos.x = fieldDim.pos.x + fieldDim.size.x - 1;

            //Apply field fix Y
            if (newPos.y >= fieldDim.size.y + fieldDim.pos.y)
                newPos.y = fieldDim.pos.y;
            else if (newPos.y <= fieldDim.pos.y)
                newPos.y = fieldDim.pos.y + fieldDim.size.y - 1;

            if (!m_bDidSnakeEat)
                m_body.RemoveLast();

            m_bDidSnakeEat = false;

            this.m_head = new SnakeBody(newPos, m_game);
            m_body.AddFirst(m_head);
        }

        /*
         * Moves snake to newPos
         * Usage: Use based on input
         */
        public void MoveSnake(Vector2d newPos)
        {
            //m_lastHead = m_head;  

            Dimensions2d fieldDim = m_playField.GetDimensions();

            //Apply field fix X
            if (newPos.x >= fieldDim.size.x + fieldDim.pos.x)
                newPos.x = fieldDim.pos.x;
            else if (newPos.x <= fieldDim.pos.x)
                newPos.x = fieldDim.pos.x + fieldDim.size.x - 1;

            //Apply field fix Y
            if (newPos.y >= fieldDim.size.y + fieldDim.pos.y)
                newPos.y = fieldDim.pos.y;
            else if (newPos.y <= fieldDim.pos.y)
                newPos.y = fieldDim.pos.y + fieldDim.size.y - 1;

            Vector2d oldMoveVec = m_vecLastMoveVec * -1;
            Vector2d newMoveVec = newPos - m_head.GetPos();

            if (oldMoveVec == newMoveVec)
            {
                MoveSnake();
                return;
            }

            m_vecLastMoveVec = newMoveVec;

            if (!m_bDidSnakeEat)
                m_body.RemoveLast();

            m_bDidSnakeEat = false;

            /*if (newPos == m_lastHead.GetPos())
                return;*/

            this.m_head = new SnakeBody(newPos, m_game);
            m_body.AddFirst(m_head);
        }
#else

        /*
         * New movement code
         */
        public void MoveSnake(Vector2d movementVec)
        {
            if (m_game.GetGameRules().IsGameOver())
                return;

            Dimensions2d fieldDim = m_playField.GetDimensions();

            Vector2d invertedLastMoveVec = m_vecLastMoveVec * -1;

            if (invertedLastMoveVec == movementVec || movementVec == new Vector2d(0, 0))
                movementVec = m_vecLastMoveVec;

            m_vecLastTailPos = m_body.Last.Value.GetPos();

            if (!m_bDidSnakeEat)
                m_body.RemoveLast();

            m_bDidSnakeEat = false;

            Vector2d newPos = movementVec + m_head.GetPos();

            //m_head.SetSprite("body.png");

            this.m_head = new SnakeBody(newPos, m_game);
            m_body.AddFirst(m_head);

           // m_head.SetSprite("head.png");

            m_vecLastMoveVec = movementVec;


            m_iSmoothingLeft = Constants.SPRITE_SIZE;
        }
#endif

        /*
         * Warp snake to the other edge of the field
         */
        public void WarpSnake()
        {
            Dimensions2d fieldDim = m_playField.GetDimensions();
            Vector2d newPos = m_head.GetPos();

            if (newPos.x < fieldDim.pos.x)
                newPos.x = fieldDim.pos.x + fieldDim.size.x - newPos.x;
            else if (newPos.x > fieldDim.pos.x + fieldDim.size.x)
                newPos.x = fieldDim.pos.x - (newPos.x - fieldDim.size.x) + 1;
            
            if (newPos.y < fieldDim.pos.y)
                newPos.y = fieldDim.pos.y + fieldDim.size.y - newPos.y;
            else if (newPos.y > fieldDim.pos.y + fieldDim.size.y)
                newPos.y = fieldDim.pos.y - (newPos.y - fieldDim.size.y) + 1;
        }

        /*
         * Returns snake head
         */
        public SnakeBody GetSnakeHead() { return m_head; }
    }

    internal class SnakeBody : BaseEntity
    {
        /*
         * Constructor
         * Parameters:
         *      posMainPanel - Position of the snake body part
         */
        public SnakeBody(Vector2d pos, Game game)
            : base(pos, game)
        {
            base.SetSprite(Constants.SPRITE_BODY);
        }

        /*
         * Draws snake body with applied movement smoothness
         */
        internal void Draw(LinkedListNode<SnakeBody> curBody)
        {
            Vector2d nextPos = GetPos() * Constants.SPRITE_SIZE;
            bool isHead = (m_game.GetSnake().GetSnakeHead() == curBody.Value);

            Vector2d curPos = null;
            if (curBody.Next == null)
                curPos = m_game.GetSnake().GetLastTailPos() * Constants.SPRITE_SIZE;
            else
                curPos = curBody.Next.Value.GetPos() * Constants.SPRITE_SIZE;

            int smoothFactor = m_game.GetSnake().GetSmooth();

            Vector2d delta = nextPos - curPos;

            //HACK: Too lazy to fix this properly, so this work around is here
            if (Math.Abs(delta.x) > Constants.SPRITE_SIZE || Math.Abs(delta.y) > Constants.SPRITE_SIZE)
                return;

            if (delta.x > 0)
                delta.x -= smoothFactor;
            else if (delta.x < 0)
                delta.x += smoothFactor;

            if (delta.y > 0)
                delta.y -= smoothFactor;
            else if (delta.y < 0)
                delta.y += smoothFactor;

            curPos = curPos += delta;
           
            if (m_sprite == null)
            {
                Console.Error.WriteLine("Missing sprite.");
                return;
            }
            if (m_game.GetGraphics() == null)
                return;

            m_game.GetGraphics().DrawImage(m_sprite, new Point(curPos.x, curPos.y));

            if (isHead)
            {
                Vector2d lightPos = curPos + new Vector2d(Constants.SPRITE_SIZE / 2, Constants.SPRITE_SIZE / 2);
                m_game.CreateSpotlight(lightPos);
            }
        }
    }
}
